﻿using Abide_AddOn_API.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace Abide_AddOn_API
{
    [Serializable]
    public class AddOnFactory : MarshalByRefObject
    {
        public string AddOnDirectory
        {
            get;
            set;
        }
        public Dictionary<string, List<string>> Tools
        {
            get;
            protected set;
        }
        public Dictionary<string, List<string>> TabPages
        {
            get;
            protected set;
        }
        public Dictionary<string, List<string>> MenuButtons
        {
            get;
            protected set;
        }
        public KeyValuePair<string, string> SettingsPage
        {
            get;
            protected set;
        }
        public List<string> FoundTypes
        {
            get;
            protected set;
        }

        public AddOnFactory()
        {
            //Prepare
            this.Tools = new Dictionary<string, List<string>>();
            this.TabPages = new Dictionary<string, List<string>>();
            this.MenuButtons = new Dictionary<string, List<string>>();
            this.FoundTypes = new List<string>();
            this.AddOnDirectory = string.Empty;

            //Resolve
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_Resolve;
            AppDomain.CurrentDomain.TypeResolve += CurrentDomain_Resolve;
        }
        public void LoadAssembly(string assemblyFile)
        {
            //Load Assembly
            LoadAssembly(Assembly.LoadFrom(assemblyFile));
        }
        public void LoadAssemblySafe(string assemblyFile)
        {
            //Read
            FileStream fs = new FileStream(assemblyFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            byte[] raw = new byte[fs.Length];
            fs.Read(raw, 0, raw.Length);
            fs.Close(); fs.Dispose();

            //Load Assembly
            LoadAssembly(Assembly.Load(raw));

            //Cleanup
            GC.Collect();
        }
        public void LoadAssembly(Assembly assembly)
        {
            //Prepare
            Type[] types = assembly.GetExportedTypes();
            string assemblyName = assembly.GetName().Name;

            //Check Types
            foreach (Type type in types)
            {
                if (!type.IsAbstract)
                {
                    //Get Interfaces
                    var Tool = type.GetInterface("ITool");
                    var TabPage = type.GetInterface("ITabPage");
                    var MenuButton = type.GetInterface("IMenuButton");
                    var Settings = type.GetInterface("ISettingsPage");
                    bool failed = false;

                    //Check Interface Implementation
                    if (Settings != null)
                    {
                        //Create
                        SettingsPage = new KeyValuePair<string, string>(assemblyName, type.FullName);
                        FoundTypes.Add(type.FullName);
                    }
                    else if (Tool != null)
                    {
                        //Create?
                        if (!Tools.ContainsKey(assemblyName))
                            Tools.Add(assemblyName, new List<string>());

                        //Append
                        Tools[assemblyName].Add(type.FullName);
                        FoundTypes.Add(type.FullName);
                    }
                    else if (TabPage != null)
                    {
                        //Create?
                        if (!TabPages.ContainsKey(assemblyName))
                            TabPages.Add(assemblyName, new List<string>());

                        //Append
                        TabPages[assemblyName].Add(type.FullName);
                        FoundTypes.Add(type.FullName);
                    }
                    else if (MenuButton != null)
                    {
                        //Create?
                        if (!MenuButtons.ContainsKey(assemblyName))
                            MenuButtons.Add(assemblyName, new List<string>());

                        //Append
                        MenuButtons[assemblyName].Add(type.FullName);
                        FoundTypes.Add(type.FullName);
                    }
                    else
                        failed = true;

                    //Check
                    if (!failed)
                        Console.WriteLine("AddOnFactory: Found type {0} in assembly {1}", type.FullName, assemblyName);
                }
            }
        }
        public T CreateInstance<T>(string assemblyName, string typeName)
            where T : class, IAddOn
        {
            try
            {
                //Get
                T obj = AppDomain.CurrentDomain.CreateInstance(assemblyName, typeName).Unwrap() as T;
                obj.Directory = AddOnDirectory;

                //Return
                return obj;
            }
            catch (TargetInvocationException ex)
            {
                //Write
                Console.WriteLine("Error creating instance of {0} in assembly {1}", typeName, assemblyName);
                Console.WriteLine("Exception: {0}", ex.InnerException.ToString());
                Console.WriteLine(ex.InnerException.StackTrace);

                //Return Null
                return null;
            }
            catch (Exception ex)
            {
                //Write
                Console.WriteLine("Error creating instance of {0} in assembly {1}", typeName, assemblyName);
                Console.WriteLine("Exception: {0}", ex.ToString());
                Console.WriteLine(ex.StackTrace);

                //Return Null
                return null;
            }
        }
        public ISettingsPage CreateSettingsPage()
        {
            try
            {
                //Get
                ISettingsPage obj = AppDomain.CurrentDomain.CreateInstance(SettingsPage.Key, SettingsPage.Value).Unwrap() as ISettingsPage;

                //Return
                return obj;
            }
            catch (TargetInvocationException ex)
            {
                //Write
                Console.WriteLine("Error creating instance of {0} in assembly {1}", SettingsPage.Value, SettingsPage.Key);
                Console.WriteLine("Exception: {0}", ex.InnerException.ToString());
                Console.WriteLine(ex.InnerException.StackTrace);

                //Return Null
                return null;
            }
            catch (Exception ex)
            {
                //Write
                Console.WriteLine("Error creating instance of {0} in assembly {1}", SettingsPage.Value, SettingsPage.Key);
                Console.WriteLine("Exception: {0}", ex.ToString());
                Console.WriteLine(ex.StackTrace);

                //Return Null
                return null;
            }
        }
        private Assembly CurrentDomain_Resolve(object sender, ResolveEventArgs args)
        {
            //Load
            string libraryLocation = Path.Combine(AddOnDirectory, args.Name + ".dll");
            string executableLocation = Path.Combine(AddOnDirectory, args.Name + ".exe");

            //Check
            if (File.Exists(libraryLocation))
                return Assembly.LoadFrom(libraryLocation);
            else if (File.Exists(executableLocation))
                return Assembly.LoadFrom(executableLocation);
            else
                return null;
        }
    }
}
